const express = require('express');
const rateLimit = require('express-rate-limit');
const { body, validationResult } = require('express-validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { User } = require('../models');
const { authenticate, authorize } = require('../middleware/auth');
const { AppError, ValidationError } = require('../middleware/errorHandler');
const config = require('../config/config');
const logger = require('../config/logger');
const redis = require('../config/redis');

const router = express.Router();

// 速率限制配置
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 5, // 最多5次尝试
  message: {
    error: '登录尝试过于频繁，请15分钟后再试'
  },
  standardHeaders: true,
  legacyHeaders: false
});

const registerLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1小时
  max: 3, // 最多3次注册
  message: {
    error: '注册尝试过于频繁，请1小时后再试'
  },
  standardHeaders: true,
  legacyHeaders: false
});

const forgotPasswordLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1小时
  max: 3, // 最多3次请求
  message: {
    error: '密码重置请求过于频繁，请1小时后再试'
  },
  standardHeaders: true,
  legacyHeaders: false
});

// 验证规则
const registerValidation = [
  body('username')
    .isLength({ min: 3, max: 50 })
    .withMessage('用户名长度必须在3-50个字符之间')
    .isAlphanumeric()
    .withMessage('用户名只能包含字母和数字')
    .custom(async (value) => {
      const user = await User.findOne({ where: { username: value.toLowerCase() } });
      if (user) {
        throw new Error('用户名已存在');
      }
      return true;
    }),
  
  body('email')
    .isEmail()
    .withMessage('请输入有效的邮箱地址')
    .normalizeEmail()
    .custom(async (value) => {
      const user = await User.findOne({ where: { email: value } });
      if (user) {
        throw new Error('邮箱已被注册');
      }
      return true;
    }),
  
  body('password')
    .isLength({ min: config.security.passwordMinLength, max: config.security.passwordMaxLength })
    .withMessage(`密码长度必须在${config.security.passwordMinLength}-${config.security.passwordMaxLength}个字符之间`)
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, 'i')
    .withMessage('密码必须包含大小写字母、数字和特殊字符'),
  
  body('confirmPassword')
    .custom((value, { req }) => {
      if (value !== req.body.password) {
        throw new Error('确认密码不匹配');
      }
      return true;
    }),
  
  body('realName')
    .optional()
    .isLength({ min: 1, max: 50 })
    .withMessage('真实姓名长度必须在1-50个字符之间'),
  
  body('phone')
    .optional()
    .matches(/^[1][3-9]\d{9}$/)
    .withMessage('请输入有效的手机号码')
    .custom(async (value) => {
      if (value) {
        const user = await User.findOne({ where: { phone: value } });
        if (user) {
          throw new Error('手机号已被注册');
        }
      }
      return true;
    })
];

const loginValidation = [
  body('identifier')
    .notEmpty()
    .withMessage('请输入用户名或邮箱'),
  
  body('password')
    .notEmpty()
    .withMessage('请输入密码')
];

const forgotPasswordValidation = [
  body('email')
    .isEmail()
    .withMessage('请输入有效的邮箱地址')
    .normalizeEmail()
];

const resetPasswordValidation = [
  body('token')
    .notEmpty()
    .withMessage('重置令牌不能为空'),
  
  body('password')
    .isLength({ min: config.security.passwordMinLength, max: config.security.passwordMaxLength })
    .withMessage(`密码长度必须在${config.security.passwordMinLength}-${config.security.passwordMaxLength}个字符之间`)
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, 'i')
    .withMessage('密码必须包含大小写字母、数字和特殊字符'),
  
  body('confirmPassword')
    .custom((value, { req }) => {
      if (value !== req.body.password) {
        throw new Error('确认密码不匹配');
      }
      return true;
    })
];

const changePasswordValidation = [
  body('currentPassword')
    .notEmpty()
    .withMessage('请输入当前密码'),
  
  body('newPassword')
    .isLength({ min: config.security.passwordMinLength, max: config.security.passwordMaxLength })
    .withMessage(`新密码长度必须在${config.security.passwordMinLength}-${config.security.passwordMaxLength}个字符之间`)
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, 'i')
    .withMessage('新密码必须包含大小写字母、数字和特殊字符'),
  
  body('confirmPassword')
    .custom((value, { req }) => {
      if (value !== req.body.newPassword) {
        throw new Error('确认密码不匹配');
      }
      return true;
    })
];

// 工具函数
const generateTokens = (user) => {
  const payload = {
    id: user.id,
    username: user.username,
    email: user.email,
    role: user.role
  };
  
  const accessToken = jwt.sign(payload, config.jwt.secret, {
    expiresIn: config.jwt.expiresIn,
    issuer: config.jwt.issuer,
    audience: config.jwt.audience
  });
  
  const refreshToken = jwt.sign(payload, config.jwt.refreshSecret, {
    expiresIn: config.jwt.refreshExpiresIn,
    issuer: config.jwt.issuer,
    audience: config.jwt.audience
  });
  
  return { accessToken, refreshToken };
};

const handleValidationErrors = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    const errorMessages = errors.array().map(error => error.msg);
    throw new ValidationError('验证失败', errorMessages);
  }
  next();
};

// 路由处理函数

/**
 * @route   POST /api/auth/register
 * @desc    用户注册
 * @access  Public
 */
router.post('/register', registerLimiter, registerValidation, handleValidationErrors, async (req, res, next) => {
  try {
    const { username, email, password, realName, phone, role = 'student' } = req.body;
    
    // 创建用户
    const user = await User.create({
      username: username.toLowerCase(),
      email: email.toLowerCase(),
      password,
      realName,
      phone,
      role,
      status: 'active'
    });
    
    // 生成邮箱验证令牌
    const verificationToken = user.generateEmailVerificationToken();
    await user.save();
    
    // 这里可以发送验证邮件
    // await sendVerificationEmail(user.email, verificationToken);
    
    // 生成JWT令牌
    const tokens = generateTokens(user);
    
    // 将刷新令牌存储到Redis
    await redis.setex(`refresh_token:${user.id}`, config.jwt.refreshExpiresIn, tokens.refreshToken);
    
    logger.request('用户注册成功', {
      userId: user.id,
      username: user.username,
      email: user.email,
      ip: req.ip
    });
    
    res.status(201).json({
      success: true,
      message: '注册成功',
      data: {
        user: user.toJSON(),
        tokens
      }
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/login
 * @desc    用户登录
 * @access  Public
 */
router.post('/login', authLimiter, loginValidation, handleValidationErrors, async (req, res, next) => {
  try {
    const { identifier, password, rememberMe = false } = req.body;
    
    // 查找用户
    const user = await User.findByEmailOrUsername(identifier);
    if (!user) {
      throw new AppError('用户名或密码错误', 401);
    }
    
    // 检查账户状态
    if (user.status !== 'active') {
      throw new AppError('账户已被禁用', 401);
    }
    
    // 检查账户是否被锁定
    if (user.isLocked()) {
      throw new AppError('账户已被锁定，请稍后再试', 401);
    }
    
    // 验证密码
    const isValidPassword = await user.validatePassword(password);
    if (!isValidPassword) {
      await user.incrementLoginAttempts();
      throw new AppError('用户名或密码错误', 401);
    }
    
    // 重置登录尝试次数
    await user.resetLoginAttempts();
    
    // 更新最后登录信息
    await user.updateLastLogin(req.ip);
    
    // 生成JWT令牌
    const tokens = generateTokens(user);
    
    // 设置刷新令牌过期时间
    const refreshExpiry = rememberMe ? config.jwt.refreshExpiresIn : config.jwt.expiresIn;
    
    // 将刷新令牌存储到Redis
    await redis.setex(`refresh_token:${user.id}`, refreshExpiry, tokens.refreshToken);
    
    logger.request('用户登录成功', {
      userId: user.id,
      username: user.username,
      email: user.email,
      ip: req.ip,
      userAgent: req.get('User-Agent')
    });
    
    res.json({
      success: true,
      message: '登录成功',
      data: {
        user: user.toJSON(),
        tokens
      }
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/logout
 * @desc    用户登出
 * @access  Private
 */
router.post('/logout', authenticate, async (req, res, next) => {
  try {
    const userId = req.user.id;
    
    // 从Redis中删除刷新令牌
    await redis.del(`refresh_token:${userId}`);
    
    // 将访问令牌加入黑名单
    const token = req.token;
    const decoded = jwt.decode(token);
    const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);
    
    if (expiresIn > 0) {
      await redis.setex(`blacklist:${token}`, expiresIn, 'true');
    }
    
    logger.request('用户登出', {
      userId,
      ip: req.ip
    });
    
    res.json({
      success: true,
      message: '登出成功'
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/refresh
 * @desc    刷新访问令牌
 * @access  Public
 */
router.post('/refresh', async (req, res, next) => {
  try {
    const { refreshToken } = req.body;
    
    if (!refreshToken) {
      throw new AppError('刷新令牌不能为空', 401);
    }
    
    // 验证刷新令牌
    const decoded = jwt.verify(refreshToken, config.jwt.refreshSecret);
    
    // 检查Redis中是否存在该刷新令牌
    const storedToken = await redis.get(`refresh_token:${decoded.id}`);
    if (!storedToken || storedToken !== refreshToken) {
      throw new AppError('无效的刷新令牌', 401);
    }
    
    // 查找用户
    const user = await User.findByPk(decoded.id);
    if (!user || user.status !== 'active') {
      throw new AppError('用户不存在或已被禁用', 401);
    }
    
    // 生成新的令牌
    const tokens = generateTokens(user);
    
    // 更新Redis中的刷新令牌
    await redis.setex(`refresh_token:${user.id}`, config.jwt.refreshExpiresIn, tokens.refreshToken);
    
    res.json({
      success: true,
      message: '令牌刷新成功',
      data: {
        tokens
      }
    });
  } catch (error) {
    if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
      next(new AppError('无效的刷新令牌', 401));
    } else {
      next(error);
    }
  }
});

/**
 * @route   POST /api/auth/forgot-password
 * @desc    忘记密码
 * @access  Public
 */
router.post('/forgot-password', forgotPasswordLimiter, forgotPasswordValidation, handleValidationErrors, async (req, res, next) => {
  try {
    const { email } = req.body;
    
    const user = await User.findOne({ where: { email } });
    if (!user) {
      // 为了安全，即使用户不存在也返回成功消息
      return res.json({
        success: true,
        message: '如果该邮箱已注册，您将收到密码重置邮件'
      });
    }
    
    // 生成密码重置令牌
    const resetToken = user.generatePasswordResetToken();
    await user.save();
    
    // 这里可以发送密码重置邮件
    // await sendPasswordResetEmail(user.email, resetToken);
    
    logger.request('密码重置请求', {
      userId: user.id,
      email: user.email,
      ip: req.ip
    });
    
    res.json({
      success: true,
      message: '如果该邮箱已注册，您将收到密码重置邮件'
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/reset-password
 * @desc    重置密码
 * @access  Public
 */
router.post('/reset-password', resetPasswordValidation, handleValidationErrors, async (req, res, next) => {
  try {
    const { token, password } = req.body;
    
    const user = await User.findByPasswordResetToken(token);
    if (!user) {
      throw new AppError('无效或已过期的重置令牌', 400);
    }
    
    // 更新密码并清除重置令牌
    await user.update({
      password,
      passwordResetToken: null,
      passwordResetExpires: null,
      loginAttempts: 0,
      lockUntil: null
    });
    
    // 清除所有刷新令牌
    await redis.del(`refresh_token:${user.id}`);
    
    logger.request('密码重置成功', {
      userId: user.id,
      email: user.email,
      ip: req.ip
    });
    
    res.json({
      success: true,
      message: '密码重置成功'
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/change-password
 * @desc    修改密码
 * @access  Private
 */
router.post('/change-password', authenticate, changePasswordValidation, handleValidationErrors, async (req, res, next) => {
  try {
    const { currentPassword, newPassword } = req.body;
    const userId = req.user.id;
    
    const user = await User.findByPk(userId);
    if (!user) {
      throw new AppError('用户不存在', 404);
    }
    
    // 验证当前密码
    const isValidPassword = await user.validatePassword(currentPassword);
    if (!isValidPassword) {
      throw new AppError('当前密码错误', 400);
    }
    
    // 更新密码
    await user.update({ password: newPassword });
    
    // 清除所有刷新令牌，强制重新登录
    await redis.del(`refresh_token:${userId}`);
    
    logger.request('密码修改成功', {
      userId,
      ip: req.ip
    });
    
    res.json({
      success: true,
      message: '密码修改成功，请重新登录'
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/verify-email
 * @desc    验证邮箱
 * @access  Public
 */
router.post('/verify-email', async (req, res, next) => {
  try {
    const { token } = req.body;
    
    if (!token) {
      throw new AppError('验证令牌不能为空', 400);
    }
    
    const user = await User.findByEmailVerificationToken(token);
    if (!user) {
      throw new AppError('无效或已过期的验证令牌', 400);
    }
    
    // 更新邮箱验证状态
    await user.update({
      emailVerified: true,
      emailVerificationToken: null,
      emailVerificationExpires: null
    });
    
    logger.request('邮箱验证成功', {
      userId: user.id,
      email: user.email,
      ip: req.ip
    });
    
    res.json({
      success: true,
      message: '邮箱验证成功'
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   POST /api/auth/resend-verification
 * @desc    重新发送验证邮件
 * @access  Private
 */
router.post('/resend-verification', authenticate, async (req, res, next) => {
  try {
    const userId = req.user.id;
    
    const user = await User.findByPk(userId);
    if (!user) {
      throw new AppError('用户不存在', 404);
    }
    
    if (user.emailVerified) {
      throw new AppError('邮箱已验证', 400);
    }
    
    // 生成新的验证令牌
    const verificationToken = user.generateEmailVerificationToken();
    await user.save();
    
    // 这里可以发送验证邮件
    // await sendVerificationEmail(user.email, verificationToken);
    
    logger.request('重新发送验证邮件', {
      userId,
      email: user.email,
      ip: req.ip
    });
    
    res.json({
      success: true,
      message: '验证邮件已发送'
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   GET /api/auth/me
 * @desc    获取当前用户信息
 * @access  Private
 */
router.get('/me', authenticate, async (req, res, next) => {
  try {
    const user = await User.findByPk(req.user.id);
    if (!user) {
      throw new AppError('用户不存在', 404);
    }
    
    res.json({
      success: true,
      data: {
        user: user.toJSON()
      }
    });
  } catch (error) {
    next(error);
  }
});

/**
 * @route   GET /api/auth/check-token
 * @desc    检查令牌有效性
 * @access  Private
 */
router.get('/check-token', authenticate, (req, res) => {
  res.json({
    success: true,
    message: '令牌有效',
    data: {
      user: req.user
    }
  });
});

module.exports = router;